// This Pine Script® code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © HPotter
////////////////////////////////////////////////////////////
// Copyright by HPotter v1.01 21/10/2026
//The Adaptive Inertia Ribbon (AIR) is a next-generation trend and momentum indicator that 
// reimagines the classic moving average ribbon. Unlike static ribbons that use fixed periods,
// AIR dynamically expands, contracts, and shifts its mathematical depth based on current market
// volatility and directed price inertia. 
//@version=6
indicator('Adaptive Inertia Ribbon', shorttitle = 'AIR', overlay = true)

//Inputs
var string G_RIBBON = 'Ribbon Settings'
var string G_ADAPT = 'Adaptivity (Inertia)'
var string G_SIGNALS = 'Signals & Visuals'

src = input.source(close, title = 'Source', group = G_RIBBON)
basePeriod = input.int(20, title = 'Base Period (Fast MA)', minval = 2, group = G_RIBBON)
step = input.int(5, title = 'Step Between Lines', minval = 1, group = G_RIBBON)
ribbonCount = input.int(8, title = 'Line Count (2-10)', minval = 2, maxval = 10, group = G_RIBBON)

inertiaLen = input.int(20, title = 'Inertia Calculation Period', minval = 5, group = G_ADAPT)
minMult = input.float(0.5, title = 'Min Adaptivity Multiplier', minval = 0.1, step = 0.1, group = G_ADAPT)
maxMult = input.float(2.0, title = 'Max Adaptivity Multiplier', minval = 1.0, step = 0.1, group = G_ADAPT)

showSignals = input.bool(true, title = 'Show Ribbon Breakout Signals', group = G_SIGNALS)

//Custom ema
var array<float> ema_hist = array.new_float(10, na)

f_dynamic_ema(source, len, index) =>
    alpha = 2.0 / (len + 1.0)
    prev_ema = array.get(ema_hist, index)
    current_ema = nz(prev_ema, source) * (1.0 - alpha) + source * alpha
    array.set(ema_hist, index, current_ema)
    current_ema

//Calculate
price_change = close - close[1]
abs_price_change = math.abs(price_change)
smoothed_change = ta.ema(price_change, inertiaLen)
smoothed_abs_change = ta.ema(abs_price_change, inertiaLen)
rvi = smoothed_abs_change != 0 ? smoothed_change / smoothed_abs_change : 0

inertiaFactor = minMult + (maxMult - minMult) * (1.0 - math.abs(rvi))

ma_values = array.new_float(10, na)

for i = 0 to ribbonCount - 1 by 1
    dynamicPeriod = math.max(2.0, (basePeriod + i * step) * inertiaFactor)
    val = f_dynamic_ema(src, dynamicPeriod, i)
    array.set(ma_values, i, val)

fastMA = array.get(ma_values, 0)
slowMA = array.get(ma_values, ribbonCount - 1)
trendUp = fastMA > slowMA

// Coloring
colorUp(int index) =>
    color.new(color.teal, 30 + index * 6)
colorDown(int index) =>
    color.new(color.maroon, 30 + index * 6)
getLineColor(int index) =>
    trendUp ? colorUp(index) : colorDown(index)

//Draw
get_val(int index) =>
    index < ribbonCount ? array.get(ma_values, index) : na

p0 = plot(get_val(0), title = 'Line 1', color = getLineColor(0))
p1 = plot(get_val(1), title = 'Line 2', color = getLineColor(1))
p2 = plot(get_val(2), title = 'Line 3', color = getLineColor(2))
p3 = plot(get_val(3), title = 'Line 4', color = getLineColor(3))
p4 = plot(get_val(4), title = 'Line 5', color = getLineColor(4))
p5 = plot(get_val(5), title = 'Line 6', color = getLineColor(5))
p6 = plot(get_val(6), title = 'Line 7', color = getLineColor(6))
p7 = plot(get_val(7), title = 'Line 8', color = getLineColor(7))
p8 = plot(get_val(8), title = 'Line 9', color = getLineColor(8))
p9 = plot(get_val(9), title = 'Line 10', color = getLineColor(9))

//Signals
buySignal = ta.crossover(close, fastMA) and trendUp
sellSignal = ta.crossunder(close, fastMA) and not trendUp

plotshape(showSignals and buySignal, title = 'Buy', style = shape.triangleup, location = location.belowbar, color = color.green, size = size.tiny)
plotshape(showSignals and sellSignal, title = 'Sell', style = shape.triangledown, location = location.abovebar, color = color.red, size = size.tiny)

//Alerts
alertcondition(buySignal, title="Adaptive Inertia Ribbon Buy", message="AIR Buy signal on {{exchange}}:{{ticker}}, TF: {{interval}}.")
alertcondition(sellSignal, title="Adaptive Inertia Ribbon Sell", message="AIR Sell signal on {{exchange}}:{{ticker}}, TF: {{interval}}.")